using System;
using System.Collections.Generic;
using System.Text;
using System.Globalization;

using Toenda.Foundation;
using Toenda.Foundation.Globalization.Holiday;

namespace Toenda.Foundation.Globalization {
	/// <summary>
	/// Class Calendar
	/// </summary>
	public class Calendar {
		#region Public Methods

		/// <summary>
		/// Gets the day number of week.
		/// </summary>
		/// <param name="firstDayOfWeek">The first day of week.</param>
		/// <param name="dayOfWeek">The day of week.</param>
		/// <returns></returns>
		public static int GetDayNumberOfWeek(DayOfWeek firstDayOfWeek, DayOfWeek dayOfWeek) {
			int number = 0;

			switch(dayOfWeek) {
				case DayOfWeek.Monday:
					number = 1;
					break;

				case DayOfWeek.Tuesday:
					number = 2;
					break;

				case DayOfWeek.Wednesday:
					number = 3;
					break;

				case DayOfWeek.Thursday:
					number = 4;
					break;

				case DayOfWeek.Friday:
					number = 5;
					break;

				case DayOfWeek.Saturday:
					number = 6;
					break;

				case DayOfWeek.Sunday:
					number = 7;
					break;
			}

			if(firstDayOfWeek == DayOfWeek.Sunday && dayOfWeek == DayOfWeek.Sunday) {
				number = 0;
			}
			else if(firstDayOfWeek == DayOfWeek.Sunday) {
				number += 1;
			}

			return number;
		}

		/// <summary>
		/// Get name of a month
		/// </summary>
		/// <param name="month">The month.</param>
		/// <returns></returns>
		public static string GetMonthName(int month) {
			DateTime date = new DateTime(1, month, 1);
			return date.ToString("MMMM", DateTimeFormatInfo.CurrentInfo);
		}

		/// <summary>
		/// Get name of a month
		/// </summary>
		/// <param name="date">The date.</param>
		/// <returns></returns>
		public static string GetMonthName(DateTime date) {
			return date.ToString("MMMM", DateTimeFormatInfo.CurrentInfo);
		}

		/// <summary>
		/// Get the week of a year
		/// </summary>
		/// <param name="dt">The dt.</param>
		/// <returns></returns>
		public static int GetWeekOfYear(DateTime dt) {
			GregorianCalendar gc = new GregorianCalendar();

			return gc.GetWeekOfYear(
				dt, 
				CalendarWeekRule.FirstDay, 
				DayOfWeek.Monday
			);
		}

		/// <summary>
		/// Get the week of a year
		/// </summary>
		/// <param name="dt">The dt.</param>
		/// <param name="cwr">The CWR.</param>
		/// <param name="dow">The dow.</param>
		/// <returns></returns>
		public static int GetWeekOfYear(DateTime dt, CalendarWeekRule cwr, DayOfWeek dow) {
			GregorianCalendar gc = new GregorianCalendar();

			return gc.GetWeekOfYear(
				dt, 
				cwr, 
				dow
			);
		}

		/// <summary>
		/// Gets the first day of week.
		/// </summary>
		/// <param name="dateTime">The date time.</param>
		/// <returns>the first day of the week</returns>
		public static DateTime GetFirstDayOfWeek(DateTime dateTime) {
			while(dateTime.DayOfWeek != DayOfWeek.Monday) {
				dateTime = dateTime.Subtract(new TimeSpan(1, 0, 0, 0));
			}

			return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day);
		}

		/// <summary>
		/// Gets the last day of week.
		/// </summary>
		/// <param name="dateTime">The date time.</param>
		/// <returns>the last day of the week</returns>
		public static DateTime GetLastDayOfWeek(DateTime dateTime) {
			while(dateTime.DayOfWeek != DayOfWeek.Sunday) {
				dateTime = dateTime.AddDays(1);
			}

			return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day);
		}

		/// <summary>
		/// Get a date from a calendarweek (starts at the first day of the week
		/// </summary>
		/// <param name="year">The year</param>
		/// <param name="calendarWeek">The calendar week</param>
		/// <returns></returns>
		public static DateTime GetDateFromWeekOfYear(int year, int calendarWeek) {
			//GregorianCalendar gc = new GregorianCalendar();
			//DateTime dt = new DateTime(year, 1, 1);

			//while(gc.GetWeekOfYear(dt, CalendarWeekRule.FirstDay, DayOfWeek.Monday) != calendarWeek) {
			//}

			return DateTime.Now;
		}

		/// <summary>
		/// Format time of minutes
		/// </summary>
		/// <param name="timeInMinutes">The time in minutes.</param>
		/// <returns></returns>
		public static string FormatTimeOfMinutes(double timeInMinutes) {
			return FormatTimeOfMinutes(Convert.ToInt64(Math.Round(timeInMinutes, 0)));
		}

		/// <summary>
		/// Format time of minutes
		/// </summary>
		/// <param name="timeInMinutes">The time in minutes.</param>
		/// <returns></returns>
		public static string FormatTimeOfMinutes(int timeInMinutes) {
			return FormatTimeOfMinutes(Convert.ToInt64(timeInMinutes));
		}

		/// <summary>
		/// Format time of minutes
		/// </summary>
		/// <param name="timeInMinutes">The time in minutes.</param>
		/// <returns></returns>
		public static string FormatTimeOfMinutes(long timeInMinutes) {
			double tmp = Convert.ToDouble(timeInMinutes) / 60;
			string s = tmp.ToString();
			string pre = "";

			if(s.StartsWith("-")) {
				pre = s.Substring(0, 1) + " ";
				s = s.Substring(1);
			}

			s = s.Substring(0, ( s.IndexOf(",") > 0 ? s.IndexOf(",") : s.Length ));
			long h = Convert.ToInt64(s);
			long m = Math.Abs(timeInMinutes) - ( h * 60 );

			return pre + h.ToString().PadLeft(2, '0') + ":" + m.ToString().PadLeft(2, '0');
		}

		/// <summary>
		/// Get the percent of a time value of a working day (a day has 8 hours / 480 minutes)
		/// </summary>
		/// <param name="timeInMinutes">The time in minutes.</param>
		/// <returns></returns>
		public static string ComputeAndFormatPercentOfWorkingDay(int timeInMinutes) {
			return GetPercentOfWorkingDay(
				timeInMinutes,
				8
			) + " %";
		}

		/// <summary>
		/// Get the percent of a time value of a working day
		/// </summary>
		/// <param name="timeInMinutes">The time in minutes.</param>
		/// <param name="hoursPerWorkingDay">The hours per working day.</param>
		/// <returns></returns>
		public static string ComputeAndFormatPercentOfWorkingDay(int timeInMinutes, int hoursPerWorkingDay) {
			return GetPercentOfWorkingDay(
				timeInMinutes, 
				hoursPerWorkingDay
			) + " %";
		}

		/// <summary>
		/// Get the percent of a time value of a working day (a day has 8 hours / 480 minutes)
		/// </summary>
		/// <param name="timeInMinutes">The time in minutes.</param>
		/// <returns></returns>
		public static double GetPercentOfWorkingDay(int timeInMinutes) {
			return GetPercentOfWorkingDay(
				timeInMinutes, 
				8
			);
		}

		/// <summary>
		/// Get the percent of a time value of a working day
		/// </summary>
		/// <param name="timeInMinutes">The time in minutes.</param>
		/// <param name="hoursPerWorkingDay">The hours per working day.</param>
		/// <returns></returns>
		public static double GetPercentOfWorkingDay(int timeInMinutes, int hoursPerWorkingDay) {
			return ( System.Math.Round(
				Convert.ToDouble(timeInMinutes) / ( hoursPerWorkingDay * 60 ),
				4
			) * 100 );
		}

		/// <summary>
		/// Compute and format the working day (a day has 8 hours / 480 minutes)
		/// </summary>
		/// <param name="timeInMinutes">The time in minutes.</param>
		/// <returns></returns>
		public static string ComputeAndFormatWorkingDay(int timeInMinutes) {
			return ( System.Math.Round(
				Convert.ToDouble(timeInMinutes) / 480,
				2
			) * 100 ) + " %";
		}

		/// <summary>
		/// Compute and format the working day
		/// </summary>
		/// <param name="timeInMinutes">The time in minutes.</param>
		/// <param name="hoursPerWorkingDay">The hours per working day.</param>
		/// <returns></returns>
		public static string ComputeAndFormatWorkingDay(int timeInMinutes, int hoursPerWorkingDay) {
			return ( System.Math.Round(
				Convert.ToDouble(timeInMinutes) / ( hoursPerWorkingDay * 60 ),
				2
			) * 100 ) + " %";
		}

		/// <summary>
		/// Parse a time string (eg. 120, 0:30, 0,5, 0.5) to a minutes value
		/// </summary>
		/// <param name="value">The value.</param>
		/// <returns></returns>
		public static int ParseStringToTimeInMinutes(string value) {
			return ParseStringToTimeInMinutes(value, false);
		}

		/// <summary>
		/// Parse a time string (eg. 120, 0:30, 0,5, 0.5) to a minutes value
		/// </summary>
		/// <param name="value">The value.</param>
		/// <param name="singleNumberIsHour">A value that indicates if a single number is a hour.</param>
		/// <returns></returns>
		public static int ParseStringToTimeInMinutes(string value, bool singleNumberIsHour) {
			int hour = 0;
			int min = 0;
			string[] parts;

			if(value != null
			&& value.Trim() != "") {
				try {
					if(value.Contains(":")) {
						// 0:30
						parts = value.Split(new char[] { ':' });
						hour = parts[0].ToInt32();
						min = parts[1].ToInt32();
					}
					else if(value.Contains(",")) {
						// 0,5
						parts = value.Split(new char[] { ',' });
						hour = parts[0].ToInt32();

						if(parts[1].Length > 1) {
							min = parts[1].Substring(0, 2).PadRight(2, '0').ToInt32();
						}
						else {
							min = parts[1].PadRight(2, '0').ToInt32();
						}

						min = GetMinutesByPercent(min);
					}
					else if(value.Contains(".")) {
						// 0.5
						parts = value.Split(new char[] { '.' });
						hour = parts[0].ToInt32();

						if(parts[1].Length > 1) {
							min = parts[1].Substring(0, 2).PadRight(2, '0').ToInt32();
						}
						else {
							min = parts[1].PadRight(2, '0').ToInt32();
						}

						min = GetMinutesByPercent(min);
					}
					else {
						// 30
						if(singleNumberIsHour) {
							if(value.Length == 1) {
								hour = value.ToInt32();
							}
							else {
								min = value.ToInt32();
							}
						}
						else {
							min = value.ToInt32();
						}
					}
				}
				catch(Exception) {
				}
			}

			return ( hour * 60 ) + min;
		}

		/// <summary>
		/// Get the percentage of minutes in a hour
		/// </summary>
		/// <param name="minutes">The minutes.</param>
		/// <returns></returns>
		public static double GetPercentOfHour(int minutes) {
			return Convert.ToDouble(( Convert.ToDouble(minutes) / 60 ) * 100);
		}

		/// <summary>
		/// Get the minutes by the percent value of a hour
		/// </summary>
		/// <param name="percent">The percent.</param>
		/// <returns></returns>
		public static int GetMinutesByPercent(double percent) {
			return Convert.ToInt32(
				System.Math.Round((percent / Convert.ToDouble(100) * Convert.ToDouble(60)))
			);
		}

		/// <summary>
		/// Get the UNIX timestamp
		/// </summary>
		/// <returns></returns>
		public static int DateTimeToUnixTimestamp() {
			return DateTimeToUnixTimestamp(DateTime.Now);
		}

		/// <summary>
		/// Get the UNIX timestamp
		/// </summary>
		/// <param name="choosenDate">The choosen date.</param>
		/// <returns></returns>
		public static int DateTimeToUnixTimestamp(DateTime choosenDate) {
			DateTime date1 = new DateTime(1970, 1, 1);
			DateTime date2 = choosenDate;
			TimeSpan ts = new TimeSpan(date2.Ticks - date1.Ticks);
			return ( Convert.ToInt32(ts.TotalSeconds) );
		}

		/// <summary>
		/// Get the DateTime of a UNIX timestamp
		/// </summary>
		/// <param name="timestamp">The timestamp.</param>
		/// <returns></returns>
		public static DateTime UnixTimestampToDateTime(int timestamp) {
			DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
			return dateTime.AddSeconds(timestamp);
		}

		/// <summary>
		/// Get the percentage of days in a week
		/// </summary>
		/// <param name="activeDays">The active days.</param>
		/// <param name="maxDays">The max days.</param>
		/// <returns></returns>
		public static double GetPercentOfDaysInWeek(double activeDays, double maxDays) {
			if(maxDays > 7) {
				maxDays = 7;
			}

			if(maxDays < 1) {
				maxDays = 1;
			}

			return Convert.ToDouble(( Convert.ToDouble(activeDays) / maxDays ) * 100);
		}

		/// <summary>
		/// Format the working load
		/// </summary>
		/// <param name="workingLoad">The working load.</param>
		/// <returns></returns>
		public static string FormatWorkingLoad(double workingLoad) {
			return FormatWorkingLoad(
				workingLoad,
				5,
				WorkingLoadResult.DayAsNumber
			);
		}

		/// <summary>
		/// Format the working load
		/// </summary>
		/// <param name="workingLoad">The working load.</param>
		/// <param name="maxWorkingLoad">The max working load.</param>
		/// <returns></returns>
		public static string FormatWorkingLoad(double workingLoad, double maxWorkingLoad) {
			return FormatWorkingLoad(
				workingLoad,
				maxWorkingLoad,
				WorkingLoadResult.DayAsNumber
			);
		}

		/// <summary>
		/// Format the working load
		/// </summary>
		/// <param name="workingLoad">The working load.</param>
		/// <param name="result">The result.</param>
		/// <returns></returns>
		public static string FormatWorkingLoad(double workingLoad, WorkingLoadResult result) {
			return FormatWorkingLoad(
				workingLoad,
				5,
				result
			);
		}

		/// <summary>
		/// Format the working load
		/// </summary>
		/// <param name="workingLoad">The working load.</param>
		/// <param name="maxWorkingLoad">The max working load.</param>
		/// <param name="result">The result.</param>
		/// <returns></returns>
		public static string FormatWorkingLoad(double workingLoad, double maxWorkingLoad, WorkingLoadResult result) {
			string tmp = "";

			switch(result) {
				case WorkingLoadResult.Percent:
					double percent = GetPercentOfDaysInWeek(
						workingLoad,
						maxWorkingLoad
					);

					tmp = Math.Round(percent, 2).ToString() + " %";
					break;

				case WorkingLoadResult.DayAsNumber:
					tmp = workingLoad + " / " + maxWorkingLoad;
					break;
			}

			return tmp;
		}

		/// <summary>
		/// Adds days to a datetime and ignores holidays and weekends.
		/// </summary>
		/// <param name="date">The date.</param>
		/// <param name="days">The days.</param>
		/// <returns></returns>
		public static DateTime AddWorkingDays(DateTime date, int days) {
			DateTime rslt = date;

			for(int i = 0; i < days; i++) {
				rslt = rslt.AddDays(1);

				bool isInvalidDay = ( HolidayCalendar.IsHoliday(rslt) || HolidayCalendar.IsWeekend(rslt) );

				while(isInvalidDay) {
					rslt = rslt.AddDays(1);

					isInvalidDay = ( HolidayCalendar.IsHoliday(rslt) || HolidayCalendar.IsWeekend(rslt) );
				}
			}

			return rslt;
		}

		/// <summary>
		/// Gets the working days in week.
		/// </summary>
		/// <param name="date">The date.</param>
		/// <param name="states">The states.</param>
		/// <returns></returns>
		public static int GetWorkingDaysInWeek(DateTime date, GermanState states) {
			System.Globalization.Calendar cal = new GregorianCalendar();

			int res = 1;
			int start = (int)cal.GetDayOfWeek(date);
			date = date.AddDays(( start * -1 ));

			for(int i = 0; i <= 7; i++) {
				date = date.AddDays(i);

				if(!HolidayCalendar.IsHoliday(date)
				&& !HolidayCalendar.IsWeekend(date)) {
					res++;
				}
			}

			return res;
		}

		/// <summary>
		/// Gets the working days in month.
		/// </summary>
		/// <param name="date">The date.</param>
		/// <param name="states">The states.</param>
		/// <returns></returns>
		public static int GetWorkingDaysInMonth(DateTime date, GermanState states) {
			System.Globalization.Calendar cal = new GregorianCalendar();

			int maxDays = cal.GetDaysInMonth(date.Year, date.Month);
			int days = maxDays - 8; // 4 weekends = 8 days

			for(int i = 29; i <= maxDays; i++) {
				DateTime tmp = new DateTime(date.Year, date.Month, i);

				if(HolidayCalendar.IsWeekend(tmp)
				|| HolidayCalendar.IsHoliday(tmp)) {
					days -= 1;
				}
			}

			return days;
		}

		/// <summary>
		/// Get the passed day proportion to its month
		/// </summary>
		/// <param name="date"></param>
		/// <returns></returns>
		public static double GetDayProportionToMonth(DateTime date) {
			return ( 1.0 * date.Day - 1.0 ) / GetNumberOfDaysInMonth(date);
		}

		/// <summary>
		/// Get a selectable date from a date list
		/// </summary>
		/// <param name="select"></param>
		/// <param name="dateList"></param>
		/// <returns></returns>
		public static DateTime GetDateFromList(DateSelection select, List<DateTime> dateList) {
			return GetDateFromList(select, dateList, null, false);
		}

		/// <summary>
		/// Get a selectable date from a date list
		/// </summary>
		/// <param name="select"></param>
		/// <param name="dateList"></param>
		/// <param name="excludeDate"></param>
		/// <param name="excludeOlder"></param>
		/// <returns></returns>
		public static DateTime GetDateFromList(DateSelection select, List<DateTime> dateList, DateTime? excludeDate, bool excludeOlder) {
			DateTime tmp = default(DateTime);

			if(excludeDate.HasValue) {
				switch(select) {
					case DateSelection.Max:
						tmp = DateTime.MinValue;

						foreach(DateTime date in dateList) {
							if(excludeOlder) {
								if(date >= excludeDate) {
									if(date > tmp) {
										tmp = date;
									}
								}
							}
							else {
								if(date <= excludeDate) {
									if(date > tmp) {
										tmp = date;
									}
								}
							}
						}
						break;

					case DateSelection.Min:
						tmp = DateTime.MaxValue;

						foreach(DateTime date in dateList) {
							if(excludeOlder) {
								if(date >= excludeDate) {
									if(date < tmp) {
										tmp = date;
									}
								}
							}
							else {
								if(date <= excludeDate) {
									if(date < tmp) {
										tmp = date;
									}
								}
							}
						}
						break;
				}
			}
			else {
				switch(select) {
					case DateSelection.Max:
						tmp = DateTime.MinValue;

						foreach(DateTime date in dateList) {
							if(date > tmp) {
								tmp = date;
							}
						}
						break;

					case DateSelection.Min:
						tmp = DateTime.MaxValue;

						foreach(DateTime date in dateList) {
							if(date < tmp) {
								tmp = date;
							}
						}
						break;
				}
			}

			return tmp;
		}

		/// <summary>
		/// Creates a correct date
		/// </summary>
		/// <param name="year"></param>
		/// <param name="month"></param>
		/// <param name="day"></param>
		/// <returns></returns>
		public static DateTime ParseDate(int year, int month, int day) {
			return _ParseDate(year, month, day, null, null, null);
		}

		/// <summary>
		/// Creates a correct date
		/// </summary>
		/// <param name="year"></param>
		/// <param name="month"></param>
		/// <param name="day"></param>
		/// <param name="hour"></param>
		/// <param name="minute"></param>
		/// <param name="second"></param>
		/// <returns></returns>
		public static DateTime ParseDate(int year, int month, int day, int hour, int minute, int second) {
			return _ParseDate(year, month, day, hour, minute, second);
		}

		/// <summary>
		/// Creates a correct date
		/// </summary>
		/// <param name="date"></param>
		/// <param name="hour"></param>
		/// <param name="minute"></param>
		/// <param name="second"></param>
		/// <returns></returns>
		public static DateTime ParseDate(DateTime date, int hour, int minute, int second) {
			return _ParseDate(date.Year, date.Month, date.Day, hour, minute, second);
		}

		/// <summary>
		/// Gets the day of the week as number
		/// </summary>
		/// <param name="date"></param>
		/// <returns></returns>
		public static int GetEuropeDayOfWeek(DateTime date) {
			int number = 0;

			switch(date.DayOfWeek) {
				case DayOfWeek.Monday:
					number = 1;
					break;

				case DayOfWeek.Tuesday:
					number = 2;
					break;

				case DayOfWeek.Wednesday:
					number = 3;
					break;

				case DayOfWeek.Thursday:
					number = 4;
					break;

				case DayOfWeek.Friday:
					number = 5;
					break;

				case DayOfWeek.Saturday:
					number = 6;
					break;

				case DayOfWeek.Sunday:
					number = 7;
					break;
			}

			return number;
		}

		/// <summary>
		/// Get the day in current month called by couponDay as datetime, if the 
		/// timespan to the given date is to long, take the next month
		/// </summary>
		/// <param name="couponDay"></param>
		/// <param name="date"></param>
		/// <returns></returns>
		public static DateTime GetCleanDate(short? couponDay, DateTime date) {
			if(!couponDay.HasValue || couponDay.GetValueOrDefault() == date.Day) {
				return date;
			}
			else {
				DateTime res1 = GetLastDayOfMonth(date.AddMonths(-1));

				if(res1.Day > couponDay) {
					res1 = GetFirstDayOfMonth(res1).AddDays(couponDay.GetValueOrDefault() - 1);
				}

				DateTime res2 = GetLastDayOfMonth(date);

				if(res2.Day > couponDay) {
					res2 = GetFirstDayOfMonth(res2).AddDays(couponDay.GetValueOrDefault() - 1);
				}

				DateTime res3 = GetLastDayOfMonth(date.AddMonths(1));

				if(res3.Day > couponDay) {
					res3 = GetFirstDayOfMonth(res3).AddDays(couponDay.GetValueOrDefault() - 1);
				}

				if(( date - res1 ) < ( res2 - date )) {
					return res1;
				}
				else if(( res3 - date ) < ( date - res2 )) {
					return res3;
				}
				else {
					return res2;
				}
			}
		}

		/// <summary>
		/// Get the last day of a month as a datetime
		/// </summary>
		/// <param name="date"></param>
		/// <returns></returns>
		public static DateTime GetLastDayOfMonth(DateTime date) {
			GregorianCalendar cal = new GregorianCalendar();

			int lastDay = cal.GetDaysInMonth(date.Year, date.Month);

			return date.AddDays(lastDay - date.Day);
		}

		/// <summary>
		/// Get the first day of a month as a datetime
		/// </summary>
		/// <param name="date"></param>
		/// <returns></returns>
		public static DateTime GetFirstDayOfMonth(DateTime date) {
			return date.AddDays(( date.Day - 1 ) * -1);
		}

		/// <summary>
		/// Add a quantity of date part to a date
		/// </summary>
		/// <param name="part"></param>
		/// <param name="quantity"></param>
		/// <param name="date"></param>
		/// <returns></returns>
		public static DateTime AddDatePart(string part, int quantity, DateTime date) {
			switch(part.ToLower()) {
				case "d":
					date = date.AddDays(quantity);
					break;

				case "y":
					date = date.AddYears(quantity);
					break;

				case "w":
					date = date.AddDays(quantity * 7);
					break;

				case "m":
				default:
					date = date.AddMonths(quantity);
					break;
			}

			return date;
		}

		/// <summary>
		/// Compare date1 with date2 and returns the different in a unit called by part
		/// </summary>
		/// <param name="part">Part is the unit returned and can be "d" for days, "y" for years, "w" for weeks and "m" for months</param>
		/// <param name="dateFrom"></param>
		/// <param name="dateTo"></param>
		/// <returns></returns>
		public static long DateCompareSpan(string part, DateTime dateFrom, DateTime dateTo) {
			switch(part.ToLower()) {
				case "d":
					return DateCompareSpan(DateComparePart.Day, dateFrom, dateTo);

				case "y":
					return DateCompareSpan(DateComparePart.Year, dateFrom, dateTo);

				case "w":
					return DateCompareSpan(DateComparePart.Week, dateFrom, dateTo);

				case "m":
				default:
					return DateCompareSpan(DateComparePart.Month, dateFrom, dateTo);
			}
		}

		/// <summary>
		/// Compare date1 with date2 and returns the different in a unit called by part
		/// </summary>
		/// <param name="part">Part is the unit returned and can be "d" for days, "y" for years, "w" for weeks and "m" for months</param>
		/// <param name="dateFrom"></param>
		/// <param name="dateTo"></param>
		/// <returns></returns>
		public static long DateCompareSpan(DateComparePart part, DateTime dateFrom, DateTime dateTo) {
			if(dateFrom == default(DateTime)
			|| dateTo == default(DateTime)) {
				return 0;
			}

			TimeSpan span = dateTo - dateFrom;

			switch(part) {
				case DateComparePart.Day:
					return span.Days;

				case DateComparePart.Year:
					GregorianCalendar gc = new GregorianCalendar();
					return (int)Math.Ceiling(
						( (double)span.Days / (double)gc.GetDaysInYear(dateFrom.Year) )
					);

				case DateComparePart.Week:
					return Convert.ToInt64(Math.Round(span.TotalDays / 7));

				case DateComparePart.Month:
				default:
					return ( dateTo.Month - dateFrom.Month ) + ( 12 * ( dateTo.Year - dateFrom.Year ) );
			}
		}

		/// <summary>
		/// Check if a year is a leap year
		/// </summary>
		/// <param name="year"></param>
		/// <returns></returns>
		public static bool IsLeapYear(int year) {
			GregorianCalendar gc = new GregorianCalendar();
			return gc.IsLeapYear(year);
		}

		/// <summary>
		/// Get the number of days in month
		/// </summary>
		/// <param name="date"></param>
		/// <returns></returns>
		public static int GetNumberOfDaysInMonth(DateTime date) {
			GregorianCalendar gc = new GregorianCalendar();
			return gc.GetDaysInMonth(date.Year, date.Month);
		}

		/// <summary>
		/// Get the number of leap days inside a perion
		/// </summary>
		/// <param name="dateFrom"></param>
		/// <param name="dateTo"></param>
		/// <returns></returns>
		public static int GetNumberOfLeapDays(DateTime dateFrom, DateTime dateTo) {
			GregorianCalendar gc = new GregorianCalendar();
			int count = 0;

			while(dateFrom < dateTo) {
				if(gc.IsLeapDay(dateFrom.Year, dateFrom.Month, dateFrom.Day)) {
					count++;
				}

				dateFrom = dateFrom.AddDays(1);
			}

			return count;
		}

		/// <summary>
		/// Get the number of work days
		/// </summary>
		/// <param name="dateFrom"></param>
		/// <param name="dateTo"></param>
		/// <param name="includeFirstDate"></param>
		/// <param name="includeSecondDate"></param>
		/// <param name="wd"></param>
		/// <param name="firstWeDay"></param>
		/// <returns></returns>
		public static double GetNumberOfWorkdays(
			DateTime dateFrom, DateTime dateTo, bool includeFirstDate, bool includeSecondDate,
			int wd, short firstWeDay
		) {
			double count = 0;

			int dw1 = ( ( GetEuropeDayOfWeek(dateFrom) - ( firstWeDay - wd - 1 ) ) % 7 );

			if(dw1 == 0) {
				dw1 = 7;
			}

			int dw2 = ( ( GetEuropeDayOfWeek(dateTo) - ( firstWeDay - wd - 1 ) ) % 7 );

			if(dw2 == 0) {
				dw2 = 7;
			}

			dateFrom = dateFrom.AddDays(( ( dw1 * -1 ) + ( firstWeDay - wd + 7 ) % 7 ));
			dateTo = dateTo.AddDays(( ( dw2 * -1 ) + ( firstWeDay - wd + 7 ) % 7 ));

			count = Arithmetic.GetNonNegative(
				( 7 - wd ) * DateCompareSpan(
					DateComparePart.Day,
					dateFrom,
					dateTo
				) / 7
				- 1 * Arithmetic.GetNonNegative(
					Arithmetic.GetNonNegative(dw1 - wd) - ( includeFirstDate ? 1 : 0 )
				)
				+ 1 * Arithmetic.GetNonNegative(
					Arithmetic.GetNonNegative(dw2 - wd) + ( includeSecondDate ? 1 : 0 ) - 1
				)
			);

			return count;
		}

		/// <summary>
		/// Check if a date is the last day of month
		/// </summary>
		/// <param name="date"></param>
		/// <returns></returns>
		public static bool IsLastDayOfMonth(DateTime date) {
			GregorianCalendar gc = new GregorianCalendar();

			if(date.Day == gc.GetDaysInMonth(date.Year, date.Month)) {
				return true;
			}
			else {
				return false;
			}
		}

		/// <summary>
		/// Get the average length of a year
		/// </summary>
		/// <param name="date1"></param>
		/// <param name="date2"></param>
		/// <returns></returns>
		public static double GetAverageYearLength(DateTime date1, DateTime date2) {
			if(date1.Year == date2.Year) {
				return 365 + ( IsLeapYear(date1.Year) ? 1 : 0 );
			}
			else {
				GregorianCalendar gc = new GregorianCalendar();

				long firstPart = gc.GetDaysInYear(date1.Year) - gc.GetDayOfYear(date1);
				long secondPart = gc.GetDayOfYear(date2) - 1;
				int n = date1.Year;
				long num = 0;
				long dom = 0;
				int yearLength = 0;

				while(n <= date2.Year) {
					yearLength = 365 + ( IsLeapYear(n) ? 1 : 0 );

					if(n == date1.Year) {
						num = num + firstPart * yearLength;
						dom = dom + firstPart;
					}
					else if(n == date2.Year) {
						num = num + secondPart * yearLength;
						dom = dom + secondPart;
					}
					else {
						num = num + yearLength * yearLength;
						dom = dom + yearLength;
					}

					n++;
				}

				if(num == 0 && dom == 0) {
					return yearLength;
				}

				return 1.0 * num / dom;
			}
		}

		/// <summary>
		/// Count the years between two dates
		/// </summary>
		/// <param name="dateFrom"></param>
		/// <param name="dateTo"></param>
		/// <returns></returns>
		public static double CountYears(DateTime dateFrom, DateTime dateTo) {
			double sum = 0;

			if(dateFrom.Date > dateTo.Date) {
				throw new ArgumentException("The parameter dateTo must be higher then dateFrom!");
			}
			else if(dateFrom.Date.Year == dateTo.Date.Year) {
				TimeSpan span = dateTo - dateFrom;
				sum = Math.Round(
					span.TotalDays / ( IsLeapYear(dateFrom.Year) ? 366.0 : 365.0 ),
					9
				);
			}
			else {
				//TimeSpan span = dateTo - dateFrom;
				//sum = Math.Round(
				//    sum = span.TotalDays / 365.25,
				//    9
				//);

				GregorianCalendar gc = new GregorianCalendar();

				sum = Math.Round(
					1.0 * ( ( IsLeapYear(dateFrom.Year) ? 366.0 : 365.0 ) - gc.GetDayOfYear(dateFrom) + 1 )
					/ ( IsLeapYear(dateFrom.Year) ? 366.0 : 365.0 )
					+ 1.0 * ( gc.GetDayOfYear(dateTo) - 1 )
					/ ( IsLeapYear(dateTo.Year) ? 366.0 : 365.0 )
					+ dateTo.Year - dateFrom.Year - 1,
					9
				);
			}

			return sum;
		}

		/// <summary>
		/// Creates a correct date
		/// </summary>
		/// <param name="year"></param>
		/// <param name="month"></param>
		/// <param name="day"></param>
		/// <returns></returns>
		public static DateTime CorrectDate(int year, int month, int day) {
			DateTime dt = new DateTime();
			bool correct = false;
			GregorianCalendar gc = new GregorianCalendar();

			while(!correct) {
				try {
					if(( gc.IsLeapYear(year) && month == 2 && day > 29 )) {
						day = 29;
					}
					else if(!gc.IsLeapYear(year) && month == 2 && day > 28) {
						day = 28;
					}

					dt = new DateTime(year, month, day);
					correct = true;
				}
				catch(Exception) {
					correct = false;
					day--;
				}
			}

			return dt;
		}

		#endregion

		#region Private Methods

		/// <summary>
		/// Creates a correct date
		/// </summary>
		/// <param name="year"></param>
		/// <param name="month"></param>
		/// <param name="day"></param>
		/// <param name="hour"></param>
		/// <param name="minute"></param>
		/// <param name="second"></param>
		/// <returns></returns>
		private static DateTime _ParseDate(int year, int month, int day, Nullable<int> hour, Nullable<int> minute, Nullable<int> second) {
			DateTime date = default(DateTime);

			date = date.AddYears(year - 1);
			date = date.AddMonths(month - 1);
			date = date.AddDays((double)day - 1);

			if(hour.HasValue) {
				date = date.AddHours((double)hour.Value);
			}

			if(minute.HasValue) {
				date = date.AddMinutes((double)minute.Value);
			}

			if(second.HasValue) {
				date = date.AddSeconds((double)second.Value);
			}

			return date;
		}

		#endregion
	}
}

